/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.esgi.dns;

import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

/**
 * This class is used to create a Message that carry communication
 * information inside the domain protocol.
 * 
 * http://www.ietf.org/rfc/rfc1035.txt
 * (§4. MESSAGES)
 * 
 * All communications inside of the domain protocol are carried in a single
 * format called a message.  The top level format of message is divided
 * into 5 sections (some of which are empty in certain cases) shown below:
 * 
 * +---------------------+
 * |        Header       |
 * +---------------------+
 * |       Question      | the question for the name server
 * +---------------------+
 * |        Answer       | RRs answering the question
 * +---------------------+
 * |      Authority      | RRs pointing toward an authority
 * +---------------------+
 * |      Additional     | RRs holding additional information
 * +---------------------+
 * 
 * The header section is always present.  The header includes fields that
 * specify which of the remaining sections are present, and also specify
 * whether the message is a query or a response, a standard query or some
 * other opcode, etc.
 * 
 * @author Alain Janin-Manificat <alain.janinm@hotmail.fr>
 * 
 * @see Header
 * @see Question
 * @see ResourceRecord
 */
public class Message {

        

    /**
     * Exception for label length greater than 63.
     */
    public static class LabelLengthException extends Exception {

        public LabelLengthException(String obj) {
            super("Invalid label length, greater than 63 : " + obj);
        }
    }

    /**
     * Exception for name length greater than 255.
     */
    public static class DomainNameLengthException extends Exception {

        public DomainNameLengthException(String obj) {
            super("Invalid name length, greater than 255 : " + obj);
        }
    }
    
    private static Map<String, Integer> lblOffset = new HashMap<>();
    Header header;
    Question question;

    public Message(byte[] message) throws Exception { 
       /* if(12 >message.length)throw new Exception("Error Message");
        byte[] tmpHeader = new byte[12];
        System.arraycopy(message, 0, tmpHeader, 0, 12);
        header = Header.unserialize(tmpHeader);
        int size = message.length - 12;
        if (0 >= size) {
            throw new Exception("Error Question");
        }
        byte[] tmpQuestion = new byte[size];
        System.arraycopy(message, tmpHeader.length, tmpQuestion, 0, size);
        question = Question.unserialize(tmpQuestion);
        System.out.println(header.getId());
        System.out.println(question.getqName());*/
    }

//    public byte[] toSend() {
//        byte[] h = header.encode();
//        byte[] q = question.encode();
//        byte[] response = new byte[h.length + q.length];
//        System.arraycopy(h, 0, response, 0, h.length);
//        System.arraycopy(q, 0, response, h.length, q.length);
//        return response;
//    }

    /**
     * Add the given label to the given ByteBuffer, according to the rules
     * defined in the RFC1035.
     *
     * http://www.ietf.org/rfc/rfc1035.txt
     *
     * @param label String - a label to put in the given ByteBuffer
     * @param message ByteBuffer - a buffer containing the message.
     * @throws org.esgi.dns.Message.LabelLengthException If the given label
     * length is greater than 63.
     */
    public static void setLabel(String label, ByteBuffer message) throws LabelLengthException {
        /*
         * Set one byte to represent the label size then set this label in
         * message buffer. Add 2 zeros to length byte because the label max
         * length is 63 bytes. (0 0 1 1 1 1 1 1)₂ = (63)₁₀
         */
        if (label.length() > 63) {
            throw new LabelLengthException(label);
        }
        byte octetSize = 0;
        octetSize |= 0 << 7;
        octetSize |= 0 << 6;
        octetSize |= (0x3F & label.length());
        lblOffset.put(label, message.position());
        message.put(octetSize);
        message.put(label.getBytes());
    }

    /**
     * Set a pointer to a label already present the given ByteBuffer.
     *
     * http://www.ietf.org/rfc/rfc1035.txt (§4.1.4. Message compression)
     *
     * The pointer takes the form of a two octet sequence:
     *
     * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 
     * | 1 1 |                OFFSET                   |
     * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
     *
     * The first two bits are ones. This allows a pointer to be distinguished
     * from a label, since the label must begin with two zero bits because
     * labels are restricted to 63 octets or less.
     *
     * The OFFSET field specifies an offset from the start of the message
     *
     * Pointers can only be used for occurances of a domain name where the
     * format is not class specific.
     *
     * @param label String - a label present in Message#lblOffset.
     * @param message ByteBuffer - a buffer containing the message.
     * @see Message#setLabel(java.lang.String, java.nio.ByteBuffer)
     */
    public static boolean setPointer(String label, ByteBuffer message) {
        if (lblOffset.containsKey(label)) {
            short labelPart = 0;
            labelPart |= 1 << 15;
            labelPart |= 1 << 14;
            labelPart |= (0x3FFF & lblOffset.get(label));
            message.putShort(labelPart);
            return true;
        } else {
            return false;
        }
    }

    /**
     * http://www.ietf.org/rfc/rfc1035.txt
     * (§3.1. Name space definitions)
     * 
     * Domain names in messages are expressed in terms of a sequence of labels.
     * Each label is represented as a one octet length field followed by that
     * number of octets.  Since every domain name ends with the null label of
     * the root, a domain name is terminated by a length byte of zero.  The
     * high order two bits of every length octet must be zero, and the
     * remaining six bits of the length field limit the label to 63 octets or
     * less.
     * 
     * To simplify implementations, the total length of a domain name (i.e.,
     * label octets and label length octets) is restricted to 255 octets or
     * less.
     * 
     * @param domainName     String - Domain name.
     * @param message   ByteBuffer - a buffer containing the message.
     * @throws org.esgi.dns.Message.LabelLengthException
     * @throws org.esgi.dns.Message.DomainNameLengthException 
     * @see Message#setLabel(java.lang.String, java.nio.ByteBuffer)
     * @see Message#setPointer(java.lang.String, java.nio.ByteBuffer) 
     */
    public static void setDomainName(String domainName, ByteBuffer message) throws LabelLengthException, DomainNameLengthException {
        
        //Get the current position.
        int messagePos = message.position();
        String originalCNAME = domainName;
        
        //Iterate over the label parts of domainName.           
        boolean endPointer = false;
        while (!domainName.isEmpty()) {
            endPointer = false;
            //Get first part.
            String firstPart = (domainName.indexOf(".") != -1)
                    ? domainName.substring(0, domainName.indexOf("."))
                    : domainName;
            //Remove first part from buffer.
            domainName = (domainName.indexOf(".") != -1)
                    ? domainName.substring(domainName.indexOf(".") + 1)
                    : "";

            /*
             * Check if a pointer to given label exists. If true the pointer
             * exists and has been set.
             */
            if (setPointer(firstPart, message)) {
                endPointer = true;
            } else {
                setLabel(firstPart, message);
            }

            /*
             * Check if a pointer to the rest of the string exists. If true the
             * pointer exists and has been set.
             */
            if (setPointer(domainName, message)) {
                endPointer = true;
                break;
            }
        }
        /*
         * If the last appended label is not a pointer, add the length octet for
         * the last zero length label.
         */
        if (!endPointer) {
            message.put((byte) 0);
        }
        
        if(message.position()-messagePos > 255){
            throw new DomainNameLengthException(originalCNAME);
        }
    }

}
